第9章 代理与反射

代理的概念:给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。

// 目标对象
const target = {
  id: 'target'
};
// 代理对象
const handler = {} // 空的代理方法
const proxy = new Proxy(target, handler);

// 通过代理访问对象的值
console.log(target.id); // target 
console.log(proxy.id); // target

// 给目标对象赋值,也会反应到代理对象上
target.id = 'foo'; 
console.log(target.id); // foo
console.log(proxy.id); // foo

// 给代理对象赋值,也会反映到目标对象上
proxy.id = 'bar';
console.log(target.id); // bar
console.log(proxy.id);  // bar

// 但是两者是不同的 
console.log(target === proxy); // false

使用代理的主要目的是可以定义捕获器,每个捕获器都对应一种基本操作,可以直接 或间接在代理对象上调用。在代理对象上调用这些操作时,代理会先调用捕获器函数,从而拦截并修改相应的行为。

// 目标对象
const target = {
  foo: 'bar'
};
// 代理对象
const handler = {
  // 给代理对象添加捕获器
  get() {
    return 'handler override';
  }
};
const proxy = new Proxy(target, handler);

// 通过代理访问参数就会触发捕获器
console.log(target.foo); // bar
console.log(proxy.foo); // handler override

get() 捕获器会接收三个参数:目标对象、要查询的属性和代理对象,基于这些参数可以重新设计 get() 方法的行为

// 目标对象
const target = {
  foo: 'bar'
};
// 代理对象
const handler = {
  // 给代理对象添加捕获器
  get(trapTarget, property, receiver) {
    console.log(trapTarget === target);
    console.log(property);
    console.log(receiver === proxy);
  }
};
const proxy = new Proxy(target, handler);
proxy.foo;
// true
// foo
// true

所有捕获器都可以基于自己的参数重建原始操作,但并非所有捕获器行为都像 get() 那么简单。通过调用全局 Reflect 对象上的同名方法来获取原始行为

const target = {
  foo: 'bar'
};
const handler = {
  get() {
    return Reflect.get(...arguments);
  } 
};
// handler 也可以简写为
const handler = {
  get: Reflect.get
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo);   // bar
console.log(target.foo);  // bar

如果仅仅是给目标对象套一层空代理就有些多此一举了,一般是对想要查询或修改的属性进行一些前置操作

const target = {
  foo: 'bar',
  baz: 'qux'
};

const handler = {
  get(trapTarget, property, receiver) {
    let decoration = '';
    if (property === 'foo') {
      decoration = '!!!';
    }
    return Reflect.get(...arguments) + decoration;
  }
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo);   // bar!!!
console.log(target.foo);  // bar
console.log(proxy.baz);   // qux
console.log(target.baz);  // qux

对于使用 new Proxy() 创建的普通代理来说,可以通过 revocable() 和 revoke() 方法中断其与目标对象的联系。

const target = {
  foo: 'bar'
};

const handler = {
  get() {
    return 'intercepted';
  }
};
// 创建可撤销的代理对象
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.foo);   // intercepted    
console.log(target.foo);  // bar
revoke(); // 中断代理和目标之间的联系
console.log(proxy.foo);   // 提示 TypeError 错误

代理可以拦截反射 API 的操作,这意味着可以创建一个代理,通过它去代理另一个代理。这样就可以在一个目标对象之上构建多层拦截网:

const target = {
  foo: 'bar'
};

// 第一层代理
const firstProxy = new Proxy(target, {
  get() {
    console.log('first proxy');
    return Reflect.get(...arguments);
  }  
});
// 第二层代理
const secondProxy = new Proxy(firstProxy, {
  get() {
    console.log('second proxy');
    return Reflect.get(...arguments);
  }
});

console.log(secondProxy.foo);
// second proxy
// first proxy
// bar